package de.lgohlke.serializer;

import java.io.File;
import java.io.IOException;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.ObjectBuffer;
import com.thoughtworks.xstream.XStream;

/**
 * @author lars
 */
/**
 * <p>
 * SerializeHelper class.
 * </p>
 * 
 * @author lars
 * @version $Id: $
 */
public final class SerializeHelper
{
  private final static Logger log = LoggerFactory.getLogger(SerializeHelper.class);

  private SerializeHelper()
  {
    // ok
  }

  private final static Set<Integer> loadedClasses = new TreeSet<Integer>();
  private static XStream            instance;
  // TODO
  public static Kryo                kryo;
  private final static int          maxCapacity   = 1024 * 1024 * 150;

  private final static Object       mutex         = new Object();
  static
  {
    reset();
  }

  /**
   * legt eine neue Instanz an und löscht die Kenntnis über bereits geladene Klassen
   */
  public static void reset()
  {
    synchronized (mutex)
    {
      instance = new XStream();
      instance.autodetectAnnotations(true);
      loadedClasses.clear();

      kryo = new Kryo();
      kryo.setRegistrationOptional(true);
    }
  }

  /**
   * converts an Object to XML using XStream
   * 
   * @param o
   *          a {@link java.lang.Object} object.
   * @return a {@link java.lang.String} object.
   */
  public static String toXML(final Object o)
  {
    synchronized (instance)
    {
      if (o == null)
      {
        return "";
      }
      else
      {
        loadClass(o.getClass());

        return instance.toXML(o);
      }
    }
  }

  /**
   * prints out call information :
   * <p/>
   * 
   * <pre>
   * package.class:method():line
   * 
   * String
   * <p/>
   *
   * @param o a {@link java.lang.Object} object.
   * @param step a int.
   */
  public static void _debug(final Object o, final int step)
  {
    StackTraceElement stack = new Exception().getStackTrace()[step];

    String str = o == null ? "" : o.toString();
    String line = "---------------------------------------------------------------------";

    log.info(line);
    log.info(stack.getClassName() + "::" + stack.getMethodName() + "():" + stack.getLineNumber() + "\n" + str);
  }

  /**
   * print out a debug Object using System.out and Xstream to print out an XML-representation
   * <p/>
   * giving a hint where the call comes from
   * 
   * @param o
   *          a {@link java.lang.Object} object.
   */
  public static void debugXML(final Object o)
  {
    _debug(toXML(o), 2);
  }

  /**
   * print out a debug string using System.out
   * <p/>
   * giving a hint where the call comes from
   * 
   * @param o
   *          a {@link java.lang.Object} object.
   */
  public static void debugString(final Object o)
  {
    _debug(o, 2);
  }

  /**
   * deserialisiert, bitte andere Methode nutzen
   * <p/>
   * use instead {@link SerializeHelper#fromXML(String, Class)}
   * 
   * @param o
   *          a {@link java.lang.String} object.
   * @return a {@link java.lang.Object} object.
   */
  @Deprecated
  public static Object fromXML(final String o)
  {
    synchronized (instance)
    {
      return instance.fromXML(o);
    }
  }

  /**
   * deserialisiert, prüft aber vorher, ob der Typ bereits bekannt ist
   * 
   * @param o
   *          a {@link java.lang.String} object.
   * @param type
   *          a {@link java.lang.Class} object.
   * @return a {@link java.lang.Object} object.
   */
  @SuppressWarnings("unchecked")
  public static <T> T fromXML(final String o, final Class<T> type)
  {
    synchronized (instance)
    {
      loadClass(type);
      return (T) instance.fromXML(o);
    }
  }

  public static <T> T fromXML(final File f, final Class<T> class1) throws IOException
  {
    String xml = FileUtils.readFileToString(f);
    return fromXML(xml, class1);

  }

  /**
   * <p>
   * debug.
   * </p>
   * 
   * @param bodyText
   *          a {@link java.lang.String} object.
   */
  public static void debug(final String bodyText)
  {
    debugString(bodyText);
  }

  /**
   * prüft, ob die Klasse dieses Objektes bereits bekannt ist
   * 
   * @param _class
   *          a {@link java.lang.Class} object.
   * @return a boolean.
   */
  public static boolean isLoadedClass(final Class<? extends Object> _class)
  {
    return loadedClasses.contains(_class.getCanonicalName().hashCode());
  }

  /**
   * <p>
   * loadClass.
   * </p>
   * 
   * @param _class
   *          a {@link java.lang.Class} object.
   */
  public static void loadClass(final Class<? extends Object> _class)
  {
    // wenn schon vorhanden, dann wird es nicht aufgenommen
    loadedClasses.add(_class.getCanonicalName().hashCode());
    instance.processAnnotations(_class);
  }

  /**
   * serialisiert mit Kryo in binäre Form als byte[] (schneller)
   * 
   * @param o
   *          a {@link java.lang.Object} object.
   * @throws java.io.IOException
   *           if any.
   * @return an array of byte.
   */
  public static byte[] toKryoBin(final Object o) throws IOException
  {
    tryRegisterKryoClass(o);
    synchronized (kryo)
    {
      ObjectBuffer buffer = new ObjectBuffer(kryo, maxCapacity);
      return buffer.writeClassAndObject(o);
    }
  }

  private static void tryRegisterKryoClass(final Object o)
  {
    synchronized (kryo)
    {
      if (o instanceof KryoSerializable)
      {
        kryo.setSerializer(o.getClass(), ((KryoSerializable<?>) o).getSerializer());
      }
    }
  }

  /**
   * deserialisiert mit Kryo aus einem byte[] (schneller)
   * 
   * @param serialized
   *          an array of byte.
   * @return a {@link java.lang.Object} object.
   */
  public static Object fromKryoBin(final byte[] serialized)
  {
    synchronized (kryo)
    {
      ObjectBuffer buffer = new ObjectBuffer(kryo, maxCapacity);
      return buffer.readClassAndObject(serialized);
    }
  }

  /**
   * deserialisiert mit Kryo aus einem byte[] (schneller)
   * 
   * @param serialized
   *          an array of byte.
   * @param object
   *          a {@link java.lang.Object} object.
   * @return a {@link java.lang.Object} object.
   */
  public static Object fromKryoBin(final byte[] serialized, final Object object)
  {
    tryRegisterKryoClass(object);
    return fromKryoBin(serialized);
  }

  /**
   * Klonen des Objektbaumes durch Serialisierung und Deserialisierung
   * 
   * @param o
   *          a {@link java.lang.Object} object.
   * @return a {@link java.lang.Object} object.
   */
  public static Object CloneBySerialisation(final Object o)
  {
    return SerializeHelper.fromXML(SerializeHelper.toXML(o), o.getClass());
  }
}
